﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using Geotools.Geometries;
using AzukiMap.Map;
using AzukiMap.Tools.Logger;
using AzukiMap.Renderer;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using System.Threading;
using System.Collections.Generic;

namespace AzukiMap.Renderer
{
    public class VectorRendererSL
    {
        MapCanvas _mapCanvas;

        internal MapCanvas MapStatus
        {
            get { return _mapCanvas; }
            set { _mapCanvas = value; }
        }

        public VectorRendererSL(MapCanvas mapCanvas)
        {
            _mapCanvas = mapCanvas;
        }

        FrameworkElement DrawGeometry(int id, GTPoint point, IVectorStyle style)
        {
            Logger.All(
                "VectorRendererSL.DrawGeometry(GTPoint): "
                + " id = " + id.ToString()
                );
            switch (style.PointType)
            {
                case PointDrawingType.Circle:
                    return DrawPointAsCircle(id, point, style);
                case PointDrawingType.Rect:
                    return DrawPointAsRect(id, point, style);
                case PointDrawingType.Bitmap:
                    return DrawPointAsBitmap(id, point, style);
                default:
                    return DrawPointAsCircle(id, point, style);
            }
        }

        FrameworkElement DrawPointAsCircle(int id, GTPoint point, IVectorStyle style)
        {
            Ellipse ellipse = new Ellipse();
            ellipse.Width = style.PointWidth;
            ellipse.Height = style.PointHeight;

            SetPointPosition(ellipse, point, style);
            SetVectorStyles(ellipse, style);

            _mapCanvas.Children.Add(ellipse);

            SetElementId(ellipse, id);

            return ellipse;
        }

        FrameworkElement DrawPointAsRect(int id, GTPoint point, IVectorStyle style)
        {
            Rectangle rect = new Rectangle();
            rect.Width = style.PointWidth;
            rect.Height = style.PointHeight;

            SetPointPosition(rect, point, style);
            SetVectorStyles(rect, style);

            _mapCanvas.Children.Add(rect);

            SetElementId(rect, id);

            return rect;
        }

        FrameworkElement DrawPointAsBitmap(int id, GTPoint point, IVectorStyle style)
        {
            Image img = style.PointImage;
            img.Width = style.PointWidth;
            img.Height = style.PointHeight;

            SetPointPosition(img, point, style);

            _mapCanvas.Children.Add(img);

            SetElementId(img, id);

            return img;
        }

        void SetPointPosition(UIElement pointElement, GTPoint point, IVectorStyle style)
        {
            GTPoint pixPoint = _mapCanvas.GetPixPointFromGeo(point.X, point.Y);
            double top = pixPoint.Y + style.AdjustY - style.PointHeight / 2;
            double left = pixPoint.X + style.AdjustX - style.PointWidth / 2;
            pointElement.SetValue(Canvas.TopProperty, top);
            pointElement.SetValue(Canvas.LeftProperty, left);
        }

        /// <summary>
        /// Draw LineString
        /// </summary>
        /// <param name="id"></param>
        /// <param name="lineString"></param>
        /// <param name="parent"></param>
        /// <param name="style"></param>
        FrameworkElement DrawGeometry(int id, GTLineString lineString, IVectorStyle style)
        {
            Logger.All(
                "VectorRendererSL.DrawGeometry(GTLineString): "
                + " id = " + id.ToString()
                );

            GTCoordinates coords = lineString.GetCoordinates();
            PointCollection pc = new PointCollection();
            Polyline line = new Polyline();

            foreach (GTCoordinate coord in coords)
            {
                GTPoint pixPoint = _mapCanvas.GetPixPointFromGeo(coord.X, coord.Y);
                //double x = _mapStatus.GetScreenX(coord.X);
                //double y = _mapStatus.GetScreenY(coord.Y);
                Point p = new Point(pixPoint.X, pixPoint.Y);
                pc.Add(p);
            }
            line.Points = pc;

            SetVectorStyles(line, style);
            _mapCanvas.Children.Add(line);

            SetElementId(line, id);

            return line;
        }

        /// <summary>
        /// Draw polygon. 
        /// For drawing polygon with hole, it use Path with GeometryGroup instead of Polygon.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="poly"></param>
        /// <param name="parent"></param>
        /// <param name="style"></param>
        FrameworkElement DrawGeometry(int id, GTPolygon poly, IVectorStyle style)
        {
            Logger.All(
                "VectorRendererSL.DrawGeometry(GTPolygon): "
                + " id = " + id.ToString()
                );

            // main path that contain the polygon shell and holes.
            Path path = new Path();
            GeometryGroup group = new GeometryGroup();
            group.FillRule = FillRule.EvenOdd;
            path.Data = group;

            // create a polygon "shell".
            PathGeometry shell = CreatePathGeometry(poly.Shell);
            group.Children.Add(shell);

            // create polygon "holes".
            for (int i = 0; i < poly.GetNumInteriorRing(); i++)
            {
                GTLinearRing innerRing = poly.GetInteriorRingN(i);
                PathGeometry inner = CreatePathGeometry(innerRing);
                group.Children.Add(inner);
            }

            //path.MouseLeftButtonDown += new MouseButtonEventHandler(path_MouseLeftButtonDown);
            SetVectorStyles(path, style);
            _mapCanvas.Children.Add(path);

            SetElementId(path, id);

            return path;
        }

        /// <summary>
        /// Create PathGeometry that make up polygon geometry.
        /// The return value is like below:
        ///    <PathGeometry>
        ///      <PathGeometry.Figures>
        ///       <PathFigure StartPoint="10,50">
        ///          <PathFigure.Segments>
        ///            <LineSegment Point="400,100" />
        ///            <LineSegment Point="100,140" />
        ///            <LineSegment Point="200,100" />
        ///          </PathFigure.Segments>
        ///        </PathFigure>
        ///      </PathGeometry.Figures>
        ///    </PathGeometry>
        /// </summary>
        /// <param name="ring"></param>
        /// <returns></returns>
        PathGeometry CreatePathGeometry(GTLinearRing ring)
        {
            PathGeometry pathGeometry = new PathGeometry();
            PathFigureCollection pathFigures = new PathFigureCollection();
            PathFigure pathFigure = new PathFigure();
            GTCoordinates coords = ring.GetCoordinates();

            bool isFirstPoint = true;
            foreach (GTCoordinate coord in coords)
            {
                //double x = _mapStatus.GetScreenX(coord.X);
                //double y = _mapStatus.GetScreenY(coord.Y);
                GTPoint pixPoint = _mapCanvas.GetPixPointFromGeo(coord.X, coord.Y);
                Point p = new System.Windows.Point(pixPoint.X, pixPoint.Y);
                if (isFirstPoint)
                {
                    pathFigure.StartPoint = p;
                    isFirstPoint = false;
                }
                else
                {
                    LineSegment lineSegment = new LineSegment();
                    lineSegment.Point = p;
                    pathFigure.Segments.Add(lineSegment);
                }
            }
            pathFigure.IsClosed = true;

            pathFigures.Add(pathFigure);
            pathGeometry.Figures = pathFigures;

            return pathGeometry;
        }


        /// <summary>
        /// Draw MultiPoint.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="multiPoint"></param>
        /// <param name="parent"></param>
        /// <param name="style"></param>
        List<FrameworkElement> DrawGeometry(int id, GTMultiPoint multiPoint, IVectorStyle style)
        {
            List<FrameworkElement> points = new List<FrameworkElement>();
            Logger.All(
                "VectorRendererSL.DrawGeometry(GTMultiPoint): "
                + " id = " + id.ToString()
                );

            GTGeometryCollection pointCollection = (GTGeometryCollection)multiPoint;
            for (int i = 0; i < pointCollection.Count; i++)
            {
                GTPoint point = (GTPoint)pointCollection[i];
                points.Add(DrawGeometry(id, point, style));
            }
            return points;
        }

        /// <summary>
        /// Draw MultiLineString.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="multiLineString"></param>
        /// <param name="parent"></param>
        /// <param name="style"></param>
        List<FrameworkElement> DrawGeometry(int id, GTMultiLineString multiLineString, IVectorStyle style)
        {
            List<FrameworkElement> lines = new List<FrameworkElement>();
            Logger.All(
                "VectorRendererSL.DrawGeometry(GTMultiLineString): "
                + " id = " + id.ToString()
                );

            GTGeometryCollection lineCollection = (GTGeometryCollection)multiLineString;
            for (int i = 0; i < lineCollection.Count; i++)
            {
                GTLineString line = (GTLineString)lineCollection[i];
                lines.Add(DrawGeometry(id, line, style));
            }
            return lines;
        }

        /// <summary>
        /// Draw MultiPolygon.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="multiPolygon"></param>
        /// <param name="parent"></param>
        /// <param name="style"></param>
        List<FrameworkElement> DrawGeometry(int id, GTMultiPolygon multiPolygon, IVectorStyle style)
        {
            List<FrameworkElement> polys = new List<FrameworkElement>();
            Logger.All(
                "VectorRendererSL.DrawGeometry(GTMultiPolygon): "
                + " id = " + id.ToString()
                );

            GTGeometryCollection polyCollection =
                (GTGeometryCollection)multiPolygon;
            for (int i = 0; i < polyCollection.Count; i++)
            {
                GTPolygon poly = (GTPolygon)polyCollection[i];
                polys.Add(DrawGeometry(id, poly, style));
            }
            return polys;
        }

        /// <summary>
        /// Draw Geometry.
        /// Use this when geometry type is not specified.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="geom"></param>
        /// <param name="parent"></param>
        /// <param name="style"></param>
        public List<FrameworkElement> DrawGeometry(int id, GTGeometry geom, IVectorStyle style)
        {
            List<FrameworkElement> geoms = new List<FrameworkElement>();
            if (!HasMapCanvas())
            {
                return geoms;
            }
            string type = geom.GetGeometryType();
            switch (type)
            {
                case "Polygon":
                    geoms.Add(DrawGeometry(id, (GTPolygon)geom, style));
                    break;
                case "MultiPolygon":
                    geoms.AddRange(DrawGeometry(id, (GTMultiPolygon)geom, style));
                    break;
                case "Point":
                    geoms.Add(DrawGeometry(id, (GTPoint)geom, style));
                    break;
                case "MultiPoint":
                    geoms.AddRange(DrawGeometry(id, (GTMultiPoint)geom, style));
                    break;
                case "LineString":
                    geoms.Add(DrawGeometry(id, (GTLineString)geom, style));
                    break;
                case "MultiLineString":
                    geoms.AddRange(DrawGeometry(id, (GTMultiLineString)geom, style));
                    break;
                case "GeometryCollection":
                    geoms.AddRange(DrawGeometry(id, (GTGeometryCollection)geom, style));
                    break;
                default:
                    Logger.Error("This geometry type is not supported to draw. type: '" + type + "'");
                    throw new ArgumentException("This geometry type is not supported to draw. type: '" + type + "'");
            }
            return geoms;
        }

        void SetVectorStyles(Shape shape, IVectorStyle style)
        {
            shape.Fill = style.Fill;
            shape.Opacity = style.Opacity;
            shape.OpacityMask = style.OpacityMask;
            shape.Stroke = style.Stroke;
            //shape.StrokeDashArray = style.StrokeDashArray;
            shape.StrokeDashCap = style.StrokeDashCap;
            shape.StrokeDashOffset = style.StrokeDashOffset;
            shape.StrokeEndLineCap = style.StrokeEndLineCap;
            shape.StrokeLineJoin = style.StrokeLineJoin;
            shape.StrokeMiterLimit = style.StrokeMiterLimit;
            shape.StrokeStartLineCap = style.StrokeStartLineCap;
            shape.StrokeThickness = style.StrokeThickness;
            
        }

        bool HasMapCanvas()
        {
            return (_mapCanvas != null);
        }

        void SetElementId(FrameworkElement element, int id)
        {
            element.Resources.Add("id", id);
        }

        /*
        private double getScreenX(double worldX)
        {
            double _x = worldX - _mapStatus.MinX;
            double x = _x / _mapStatus.Scale;
            return x;
        }

        private double getScreenY(double worldY)
        {
            double _y = worldY - _mapStatus.MaxY;
            double y = _y / _mapStatus.Scale * -1;
            return y;
        }

        private void setScale()
        {
            double canvasWidth = _mapStatus.MainCanvas.Width;
            double canvasHeight = _mapStatus.MainCanvas.Height;
            double geoWidth = _mapStatus.GeoWidth;
            double geoHeight = _mapStatus.GeoHeight;
            double scaleX = geoWidth / canvasWidth;
            double scaleY = geoHeight / canvasHeight;
            _mapStatus.Scale = (scaleX < scaleY) ? scaleX : scaleY;
        }
         */
    }
}
